package se.cth.hedgehogphoto.plugin; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import se.cth.hedgehogphoto.log.Log; /** * A custom class loader that loads classes and compiles them if necessary. * @author Barnabas Sapan */ public class FileClassLoader extends URLClassLoader { private File pluginRootDirectory; private Map<String, Class<?>> loadedClasses = new HashMap<String, Class<?>>(); public FileClassLoader(URL[] urls) { super(urls); if(urls.length == 1){ this.pluginRootDirectory = new File(urls[0].getPath()); this.addSubfolders(); } else { throw new MultipleURLException("Subbfolders will be added automatically," + " only supply the root foolder."); } } /** * Appends the subfolders in pluginRootDirectory * to the list of URLs to search for classes and resources. */ private void addSubfolders(){ for(File dir : this.pluginRootDirectory.listFiles()){ if(dir.isDirectory()){ try { File subfolder = new File(dir.toURI()); Log.getLogger().log(Level.INFO, "Setting sub directoy: " + subfolder.toURI().toURL()); addURL(subfolder.toURI().toURL()); } catch (IOException e) { Log.getLogger().log(Level.SEVERE, "IOException", e); } } } } /** * Loads a class from file. Location based on the root folder supplied in the constructor. * Tries to compile the .java file if the .class is nonexistent or outdated. * @param file the file name including the package like se.cth.hedgehogphoto.plugin.Tester * excluding .java or .class at the end. */ @Override public Class<?> loadClass(final String file){ Class<?> c = null; //Replace packages to a proper folderstructure final String fileStub = file.replace( '.', '/' ); final String javaFilenamePath = this.pluginRootDirectory.getAbsolutePath() + fileStub + ".java"; final String classFilenamePath = this.pluginRootDirectory.getAbsolutePath() + fileStub + ".class"; File javaFile = new File(javaFilenamePath); File classFile = new File(classFilenamePath); javaFile = Helper.findFileInSubfolder(javaFile, fileStub ,".java", getURLs()); classFile = Helper.findFileInSubfolder(classFile, fileStub ,".class", getURLs()); //Only compile if necessary if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { Log.getLogger().log(Level.INFO, ".class is outdated/nonexistent, compilation needed..."); if(compile(javaFile)){ Log.getLogger().log(Level.INFO, "Compilation succesfull!"); } else { Log.getLogger().log(Level.SEVERE, "Compilation failed!"); } } catch (IOException e) { Log.getLogger().log(Level.SEVERE, "IOException", e); } } //Try to...Return it if we already have loaded it //faster then trying to load it again. c = (Class<?>)this.loadedClasses.get(file); if (c != null) { return c; } //Try to...Find it via loadclass try { c = super.loadClass(file, true); this.loadedClasses.put(file, c); } catch (ClassNotFoundException e) { Log.getLogger().log(Level.SEVERE, "ClassNotFoundException: " + file + " not loaded!", e); } return c; } /** * Compiles a specific file. * @param fileToCompile the file to compile * @return returns true if the compilation was successful, false if it failed due * to compilation errors or if JDK is not installed on the system * @throws IOException */ private boolean compile(final File fileToCompile) throws IOException { Log.getLogger().log(Level.INFO, "Compiling " + fileToCompile.getAbsolutePath() + "..."); //Copy our API to plugin dir final Path target = Paths.get(this.pluginRootDirectory.getAbsolutePath() + System.getProperty("file.separator") + "API.jar"); if(Files.exists(target) == false) { Log.getLogger().log(Level.INFO, "Copying API.jar to: " + this.pluginRootDirectory.getAbsolutePath() + "..."); Path source = Paths.get(System.getProperty("user.dir") + System.getProperty("file.separator") + "API.jar"); Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); } else { Log.getLogger().log(Level.INFO, "API.jar allready in place, skipping copying..."); } //Handles the compiling boolean compilationResult = false; File fileRootFolder = Helper.findFolderForFile(fileToCompile); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if(compiler != null){ StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> compUnits = fileManager.getJavaFileObjects(fileToCompile); String classpath = this.pluginRootDirectory.getAbsolutePath() + System.getProperty("file.separator") + "API.jar:" + fileRootFolder.getAbsolutePath(); final Iterable<String> options = Arrays.asList(new String[] { "-cp", classpath}); compilationResult = compiler.getTask(null, fileManager, null, options, null, compUnits).call(); } else { Log.getLogger().log(Level.SEVERE, "No javacompiler found on system! ERROR"); } return compilationResult; } }